home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / hplip / hpssd.py < prev    next >
Encoding:
Python Source  |  2009-04-14  |  17.9 KB  |  552 lines

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P.
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software
  18. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  19. #
  20. # Author: Don Welch
  21. #
  22.  
  23. __version__ = '12.0'
  24. __title__ = "Services and Status System Tray dBus Child/Parent Process"
  25. __mod__ = 'hpssd'
  26. __doc__ = "Provides persistent data and event services to HPLIP client applications. Required to be running for PC send fax, optional in all other cases."
  27.  
  28.  
  29. # StdLib
  30. import sys
  31. import struct
  32. import os
  33. import time
  34. import getopt
  35. import select
  36. import signal
  37. import tempfile
  38. #import threading
  39. #import Queue
  40. from cPickle import loads, HIGHEST_PROTOCOL
  41.  
  42. # Local
  43. from base.g import *
  44. from base.codes import *
  45. from base import utils, device, status, models
  46.  
  47. # dBus
  48. try:
  49.     from dbus import lowlevel, SystemBus, SessionBus
  50.     import dbus.service
  51.     from dbus.mainloop.glib import DBusGMainLoop
  52.     from gobject import MainLoop, timeout_add, threads_init, io_add_watch, IO_IN
  53.     dbus_loaded = True
  54. except ImportError:
  55.     log.error("dbus failed to load (python-dbus ver. 0.80+ required). Exiting...")
  56.     dbus_loaded = False
  57.     sys.exit(1)
  58.  
  59.  
  60. # Globals
  61. PIPE_BUF = 4096
  62. dbus_loop, main_loop = None, None
  63. system_bus = None
  64. session_bus = None
  65. w1, w2, r3 = None, None, None
  66. devices = {} # { 'device_uri' : DeviceCache, ... }
  67.  
  68.  
  69. # ***********************************************************************************
  70. #
  71. # DEVICE CACHE
  72. #
  73. # ***********************************************************************************
  74.  
  75. class DeviceCache(object):
  76.     def __init__(self, model=''):
  77.         self.history = utils.RingBuffer(prop.history_size) # circular buffer of device.Event
  78.         self.model = models.normalizeModelName(model)
  79.         self.cache = {} # variable name : value
  80.         self.faxes = {} # (username, jobid): FaxEvent
  81.         self.dq = {} # last device query results
  82.         #self.backoff = False
  83.         self.backoff_counter = 0  # polling backoff: 0 = none, x = backed off by x intervals
  84.         self.backoff_countdown = 0
  85.         self.polling = False # indicates whether its in the device polling list
  86.  
  87.  
  88. #  dbus interface on session bus
  89. class StatusService(dbus.service.Object):
  90.     def __init__(self, name, object_path):
  91.         dbus.service.Object.__init__(self, name, object_path)
  92.  
  93.  
  94.     @dbus.service.method('com.hplip.StatusService', in_signature='s', out_signature='sa(ssisisd)')
  95.     def GetHistory(self, device_uri):
  96.         log.debug("GetHistory('%s')" % device_uri)
  97.         send_systray_blip()
  98.         try:
  99.             devices[device_uri]
  100.         except KeyError:
  101.             #log.warn("Unknown device URI: %s" % device_uri)
  102.             return (device_uri, [])
  103.         else:
  104.             h = devices[device_uri].history.get()
  105.             log.debug("%d events in history:" % len(h))
  106.             [x.debug() for x in h]
  107.             return (device_uri, [x.as_tuple() for x in h])
  108.  
  109.  
  110.     @dbus.service.method('com.hplip.StatusService', in_signature='s', out_signature='sa{ss}')
  111.     def GetStatus(self, device_uri):
  112.         log.debug("GetStatus('%s')" % device_uri)
  113.         send_systray_blip()
  114.         try:
  115.             devices[device_uri]
  116.         except KeyError:
  117.             #log.warn("Unknown device URI: %s" % device_uri)
  118.             return (device_uri, {})
  119.         else:
  120.             t = {}
  121.             dq = devices[device_uri].dq
  122.             [t.setdefault(x, str(dq[x])) for x in dq.keys()]
  123.             log.debug(t)
  124.             return (device_uri, t)
  125.  
  126.  
  127.     @dbus.service.method('com.hplip.StatusService', in_signature='ssi', out_signature='i')
  128.     def SetCachedIntValue(self, device_uri, key, value):
  129.         log.debug("SetCachedIntValue('%s', '%s', %d)" % (device_uri, key, value))
  130.         if check_device(device_uri) == ERROR_SUCCESS:
  131.             devices[device_uri].cache[key] = value
  132.             return value
  133.  
  134.         return -1
  135.  
  136.  
  137.     @dbus.service.method('com.hplip.StatusService', in_signature='ss', out_signature='i')
  138.     def GetCachedIntValue(self, device_uri, key):
  139.         try:
  140.             ret = devices[device_uri].cache[key]
  141.         except KeyError:
  142.             ret = -1
  143.  
  144.         log.debug("GetCachedIntValue('%s', '%s') --> %d" % (device_uri, key, ret))
  145.         return ret
  146.  
  147.  
  148.     @dbus.service.method('com.hplip.StatusService', in_signature='sss', out_signature='s')
  149.     def SetCachedStrValue(self, device_uri, key, value):
  150.         log.debug("SetCachedStrValue('%s', '%s', '%s')" % (device_uri, key, value))
  151.         if check_device(device_uri) == ERROR_SUCCESS:
  152.             devices[device_uri].cache[key] = value
  153.             return value
  154.  
  155.         return ''
  156.  
  157.  
  158.     @dbus.service.method('com.hplip.StatusService', in_signature='ss', out_signature='s')
  159.     def GetCachedStrValue(self, device_uri, key):
  160.         try:
  161.             ret = devices[device_uri].cache[key]
  162.         except KeyError:
  163.             ret = ''
  164.  
  165.         log.debug("GetCachedStrValue('%s', '%s') --> %s" % (device_uri, key, ret))
  166.         return ret
  167.  
  168.  
  169.     # Pass a non-zero job_id to retrieve a specific fax
  170.     # Pass zero for job_id to retrieve any avail. fax
  171.     @dbus.service.method('com.hplip.StatusService', in_signature='ssi', out_signature='ssisisds')
  172.     def CheckForWaitingFax(self, device_uri, username, job_id=0):
  173.         log.debug("CheckForWaitingFax('%s', '%s', %d)" % (device_uri, username, job_id))
  174.         send_systray_blip()
  175.         r = (device_uri, '', 0, username, job_id, '', 0.0, '')
  176.         check_device(device_uri)
  177.         show_waiting_faxes(device_uri)
  178.  
  179.         if job_id: # check for specific job_id
  180.             try:
  181.                 devices[device_uri].faxes[(username, job_id)]
  182.             except KeyError:
  183.                 return r
  184.             else:
  185.                 return self.check_for_waiting_fax_return(device_uri, username, job_id)
  186.  
  187.         else: # return any matching one from cache. call mult. times to get all.
  188.             for u, j in devices[device_uri].faxes.keys():
  189.                 if u == username:
  190.                     return self.check_for_waiting_fax_return(device_uri, u, j)
  191.  
  192.             return r
  193.  
  194.  
  195.     # if CheckForWaitingFax returns a fax job, that job is removed from the cache
  196.     def check_for_waiting_fax_return(self, d, u, j):
  197.         log.debug("Fax (username=%s, jobid=%d) removed from faxes and returned to caller." % (u, j))
  198.         r = devices[d].faxes[(u, j)].as_tuple()
  199.         del devices[d].faxes[(u, j)]
  200.         show_waiting_faxes(d)
  201.         return r
  202.  
  203.  
  204.     # Alternate way to "send" an event rather than using a signal message
  205.     @dbus.service.method('com.hplip.StatusService', in_signature='ssisis', out_signature='')
  206.     def SendEvent(self, device_uri, printer_name, event_code, username, job_id, title):
  207.         event = device.Event(device_uri, printer_name, event_code, username, job_id, title)
  208.         handle_event(event)
  209.  
  210.  
  211.  
  212. def check_device(device_uri):
  213.     try:
  214.         devices[device_uri]
  215.     except KeyError:
  216.         log.debug("New device: %s" % device_uri)
  217.         try:
  218.             back_end, is_hp, bus, model, serial, dev_file, host, port = \
  219.                 device.parseDeviceURI(device_uri)
  220.         except Error:
  221.             log.debug("Invalid device URI: %s" % device_uri)
  222.             return ERROR_INVALID_DEVICE_URI
  223.  
  224.         devices[device_uri] = DeviceCache(model)
  225.  
  226.     return ERROR_SUCCESS
  227.  
  228.  
  229. def create_history(event):
  230.     history = devices[event.device_uri].history.get()
  231.  
  232.     if history and history[-1].event_code == event.event_code:
  233.         log.debug("Duplicate event. Replacing previous event.")
  234.         devices[event.device_uri].history.replace(event)
  235.         return True
  236.     else:
  237.         devices[event.device_uri].history.append(event)
  238.         return False
  239.  
  240.  
  241.  
  242. def handle_fax_event(event, pipe_name):
  243.     if event.event_code == EVENT_FAX_RENDER_COMPLETE and \
  244.         event.username == prop.username:
  245.  
  246.         fax_file_fd, fax_file_name = tempfile.mkstemp(prefix="hpfax-")
  247.         pipe = os.open(pipe_name, os.O_RDONLY)
  248.         bytes_read = 0
  249.         while True:
  250.             data = os.read(pipe, PIPE_BUF)
  251.             if not data:
  252.                 break
  253.  
  254.             os.write(fax_file_fd, data)
  255.             bytes_read += len(data)
  256.  
  257.         log.debug("Saved %d bytes to file %s" % (bytes_read, fax_file_name))
  258.  
  259.         os.close(pipe)
  260.         os.close(fax_file_fd)
  261.  
  262.         devices[event.device_uri].faxes[(event.username, event.job_id)] = \
  263.             device.FaxEvent(fax_file_name, event)
  264.  
  265.         show_waiting_faxes(event.device_uri)
  266.  
  267.         try:
  268.             os.waitpid(-1, os.WNOHANG)
  269.         except OSError:
  270.             pass
  271.  
  272.         # See if hp-sendfax is already running for this queue
  273.         ok, lock_file = utils.lock_app('hp-sendfax-%s' % event.printer_name, True)
  274.  
  275.         if ok:
  276.             # able to lock, not running...
  277.             utils.unlock(lock_file)
  278.  
  279.             path = utils.which('hp-sendfax')
  280.             if path:
  281.                 path = os.path.join(path, 'hp-sendfax')
  282.             else:
  283.                 log.error("Unable to find hp-sendfax on PATH.")
  284.                 return
  285.  
  286.             log.debug("Running hp-sendfax: %s --printer=%s" % (path, event.printer_name))
  287.             os.spawnlp(os.P_NOWAIT, path, 'hp-sendfax',
  288.                 '--printer=%s' % event.printer_name)
  289.  
  290.         else:
  291.             # cannot lock file - hp-sendfax is running
  292.             # no need to do anything... hp-sendfax is polling
  293.             log.debug("hp-sendfax is running. Waiting for CheckForWaitingFax() call.")
  294.  
  295.     else:
  296.         log.warn("Not handled!")
  297.         pass
  298.  
  299.  
  300. def show_waiting_faxes(d):
  301.     f = devices[d].faxes
  302.  
  303.     if not len(f):
  304.         log.debug("No faxes waiting for %s" % d)
  305.     else:
  306.         if len(f) == 1:
  307.             log.debug("1 fax waiting for %s:" % d)
  308.         else:
  309.             log.debug("%d faxes waiting for %s:" % (len(f), d))
  310.  
  311.         [f[x].debug() for x in f]
  312.  
  313.  
  314. # Qt4 only
  315. def handle_hpdio_event(event, bytes_written):
  316.     log.debug("Reading %d bytes from hpdio pipe..." % bytes_written)
  317.     total_read, data = 0, ''
  318.  
  319.     while True:
  320.         r, w, e = select.select([r3], [], [r3], 0.0)
  321.         if not r: break
  322.  
  323.         x = os.read(r3, PIPE_BUF)
  324.         if not x: break
  325.  
  326.         data = ''.join([data, x])
  327.         total_read += len(x)
  328.  
  329.         if total_read == bytes_written: break
  330.  
  331.     log.debug("Read %d bytes" % total_read)
  332.  
  333.     if total_read == bytes_written:
  334.         dq = loads(data)
  335.  
  336.         if check_device(event.device_uri) == ERROR_SUCCESS:
  337.             devices[event.device_uri].dq = dq.copy()
  338.  
  339.             handle_event(device.Event(event.device_uri, '',
  340.                 dq.get('status-code', STATUS_PRINTER_IDLE), prop.username, 0, ''))
  341.  
  342.             send_toolbox_event(event, EVENT_DEVICE_UPDATE_REPLY)
  343.  
  344.  
  345.  
  346. def handle_event(event, more_args=None):
  347.     #global polling_blocked
  348.     #global request_queue
  349.  
  350.     log.debug("Handling event...")
  351.  
  352.     if more_args is None:
  353.         more_args = []
  354.  
  355.     event.debug()
  356.  
  357.     if event.device_uri and check_device(event.device_uri) != ERROR_SUCCESS:
  358.         return
  359.  
  360.     # If event-code > 10001, its a PJL error code, so convert it
  361.     if event.event_code > EVENT_MAX_EVENT:
  362.         event.event_code = status.MapPJLErrorCode(event.event_code)
  363.  
  364.     # regular user/device status event
  365.     if event.event_code < EVENT_MIN_USER_EVENT:
  366.         pass
  367.  
  368.     elif EVENT_MIN_USER_EVENT <= event.event_code <= EVENT_MAX_USER_EVENT:
  369.  
  370.         if event.device_uri:
  371.             #event.device_uri = event.device_uri.replace('hpfax:', 'hp:')
  372.             dup_event = create_history(event)
  373.  
  374.             if event.event_code in (EVENT_DEVICE_STOP_POLLING,
  375.                                     EVENT_START_MAINT_JOB,
  376.                                     EVENT_START_COPY_JOB,
  377.                                     EVENT_START_FAX_JOB,
  378.                                     EVENT_START_PRINT_JOB):
  379.                 pass # stop polling (increment counter)
  380.  
  381.             elif event.event_code in (EVENT_DEVICE_START_POLLING, # should this event force counter to 0?
  382.                                       EVENT_END_MAINT_JOB,
  383.                                       EVENT_END_COPY_JOB,
  384.                                       EVENT_END_FAX_JOB,
  385.                                       EVENT_END_PRINT_JOB,
  386.                                       EVENT_PRINT_FAILED_MISSING_PLUGIN,
  387.                                       EVENT_SCANNER_FAIL,
  388.                                       EVENT_END_SCAN_JOB,
  389.                                       EVENT_SCAN_FAILED_MISSING_PLUGIN,
  390.                                       EVENT_FAX_JOB_FAIL,
  391.                                       EVENT_FAX_JOB_CANCELED,
  392.                                       EVENT_FAX_FAILED_MISSING_PLUGIN,
  393.                                       EVENT_COPY_JOB_FAIL,
  394.                                       EVENT_COPY_JOB_CANCELED):
  395.                 pass # start polling if counter <= 0
  396.                 # TODO: Do tools send END event if canceled or failed? Should they?
  397.                 # TODO: What to do if counter doesn't hit 0 after a period? Timeout?
  398.                 # TODO: Also, need to deal with the backoff setting (or it completely sep?)
  399.  
  400.         # Send to system tray icon if available
  401.         if not dup_event: # and event.event_code != STATUS_PRINTER_IDLE:
  402.             send_event_to_systray_ui(event)
  403.  
  404.         # send EVENT_HISTORY_UPDATE signal to hp-toolbox
  405.         send_toolbox_event(event, EVENT_HISTORY_UPDATE)
  406.  
  407.     # Handle fax signals
  408.     elif EVENT_FAX_MIN <= event.event_code <= EVENT_FAX_MAX and more_args:
  409.         log.debug("Fax event")
  410.         pipe_name = str(more_args[0])
  411.         handle_fax_event(event, pipe_name)
  412.  
  413.     elif event.event_code == EVENT_USER_CONFIGURATION_CHANGED:
  414.         # Sent if polling, hiding, etc. configuration has changed
  415.     #    send_event_to_hpdio(event)
  416.         send_event_to_systray_ui(event)
  417.  
  418.     elif event.event_code == EVENT_SYS_CONFIGURATION_CHANGED: # Not implemented
  419.         #send_event_to_hpdio(event)
  420.         send_event_to_systray_ui(event)
  421.  
  422.     # Qt4 only
  423.     elif event.event_code in (EVENT_DEVICE_UPDATE_REQUESTED,):
  424.                               #EVENT_DEVICE_START_POLLING,  # ?  Who handles polling? hpssd? probably...
  425.                               #EVENT_DEVICE_STOP_POLLING):  # ?
  426.         send_event_to_hpdio(event)
  427.  
  428.     # Qt4 only
  429.     elif event.event_code in (EVENT_DEVICE_UPDATE_ACTIVE,
  430.                               EVENT_DEVICE_UPDATE_INACTIVE):
  431.         send_event_to_systray_ui(event)
  432.  
  433.     # Qt4 only
  434.     elif event.event_code == EVENT_DEVICE_UPDATE_REPLY:
  435.         bytes_written = int(more_args[1])
  436.         handle_hpdio_event(event, bytes_written)
  437.  
  438.     # Qt4 only
  439.     elif event.event_code == EVENT_SYSTEMTRAY_EXIT:
  440.         send_event_to_hpdio(event)
  441.         send_toolbox_event(event)
  442.         send_event_to_systray_ui(event)
  443.         log.debug("Exiting")
  444.         main_loop.quit()
  445.  
  446.     elif event.event_code in (EVENT_DEVICE_START_POLLING,
  447.                               EVENT_DEVICE_STOP_POLLING):
  448.         pass
  449.  
  450.     else:
  451.         log.error("Unhandled event: %d" % event.event_code)
  452.  
  453.  
  454.  
  455. def send_systray_blip():
  456.     send_event_to_systray_ui(device.Event('', '', EVENT_DEVICE_UPDATE_BLIP))
  457.  
  458.  
  459. def send_event_to_systray_ui(event, event_code=None):
  460.     e = event.copy()
  461.  
  462.     if event_code is not None:
  463.         e.event_code = event_code
  464.  
  465.     e.send_via_pipe(w1, 'systemtray')
  466.  
  467.  
  468. def send_event_to_hpdio(event):
  469.     event.send_via_pipe(w2, 'hpdio')
  470.  
  471.  
  472. def send_toolbox_event(event, event_code=None):
  473.     global session_bus
  474.  
  475.     e = event.copy()
  476.  
  477.     if event_code is not None:
  478.         e.event_code = event_code
  479.  
  480.     e.send_via_dbus(session_bus, 'com.hplip.Toolbox')
  481.  
  482.  
  483.  
  484. def handle_signal(typ, *args, **kwds):
  485.     if kwds['interface'] == 'com.hplip.StatusService' and \
  486.         kwds['member'] == 'Event':
  487.  
  488.         event = device.Event(*args[:6])
  489.         return handle_event(event, args[6:])
  490.  
  491.  
  492. def handle_system_signal(*args, **kwds):
  493.     return handle_signal('system', *args, **kwds)
  494.  
  495.  
  496. def handle_session_signal(*args, **kwds):
  497.     return handle_signal('session', *args, **kwds)
  498.  
  499.  
  500.  
  501. def run(write_pipe1=None,  # write pipe to systemtray
  502.         write_pipe2=None,  # write pipe to hpdio
  503.         read_pipe3=None):  # read pipe from hpdio
  504.  
  505.     global dbus_loop, main_loop
  506.     global system_bus, session_bus
  507.     global w1, w2, r3
  508.  
  509.     log.set_module("hp-systray(hpssd)")
  510.     log.debug("PID=%d" % os.getpid())
  511.     w1, w2, r3 = write_pipe1, write_pipe2, read_pipe3
  512.  
  513.     dbus_loop = DBusGMainLoop(set_as_default=True)
  514.     main_loop = MainLoop()
  515.  
  516.     try:
  517.         system_bus = SystemBus(mainloop=dbus_loop)
  518.     except dbus.exceptions.DBusException, e:
  519.         log.error("Unable to connect to dbus system bus. Exiting.")
  520.         sys.exit(1)
  521.  
  522.     try:
  523.         session_bus = dbus.SessionBus()
  524.     except dbus.exceptions.DBusException, e:
  525.         if os.getuid() != 0:
  526.             log.error("Unable to connect to dbus session bus. Exiting.")
  527.             sys.exit(1)
  528.         else:
  529.             log.error("Unable to connect to dbus session bus (running as root?)")
  530.             sys.exit(1)
  531.  
  532.     # Receive events from the system bus
  533.     system_bus.add_signal_receiver(handle_system_signal, sender_keyword='sender',
  534.         destination_keyword='dest', interface_keyword='interface',
  535.         member_keyword='member', path_keyword='path')
  536.  
  537.     # Receive events from the session bus
  538.     session_bus.add_signal_receiver(handle_session_signal, sender_keyword='sender',
  539.         destination_keyword='dest', interface_keyword='interface',
  540.         member_keyword='member', path_keyword='path')
  541.  
  542.     # Export an object on the session bus
  543.     session_name = dbus.service.BusName("com.hplip.StatusService", session_bus)
  544.     status_service = StatusService(session_name, "/com/hplip/StatusService")
  545.  
  546.     log.debug("Entering main dbus loop...")
  547.     try:
  548.         main_loop.run()
  549.     except KeyboardInterrupt:
  550.         log.debug("Ctrl-C: Exiting...")
  551.  
  552.